package models

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"github.com/zeromicro/go-zero/core/logx"
	"net/http"
	"sync"
	"time"
)

/**
 * @Author: way lee
 * @Date: 2024/9/23 16:29
 * @Desc:
 */

// ComponentInfo 组件信息结构体
type ComponentInfo struct {
	ClusterID      string `db:"cluster_id" json:"clusterID"`
	ComponentName  string `db:"component_name" json:"componentName"`
	Version        string `db:"version" json:"version"`
	NodeHost       string `db:"node_host" json:"nodeHost"`
	NodeIP         string `db:"node_ip" json:"nodeIP"`
	ComponentRole  string `db:"component_role" json:"componentRole"`
	HomePath       string `db:"home_path" json:"homePath,omitempty,optional"`
	DataStorageDir string `db:"data_storage_dir" json:"dataStorageDir,omitempty,optional"`
	Port           string `db:"port" json:"port,omitempty,optional"`
	Status         bool   `db:"status" json:"status,omitempty"`
	AdditionalInfo string `db:"additional_info" json:"additionalInfo,omitempty,optional"`
}

// ComponentInfoManager 管理组件信息并批量发送到 API
type ComponentInfoManager struct {
	componentInfoChannel chan *ComponentInfo
	url                  string
	wg                   sync.WaitGroup
	mu                   sync.Mutex       // 用于保护 componentInfos 的并发安全
	componentInfos       []*ComponentInfo // 存储待发送的组件信息
	done                 chan struct{}
	client               *http.Client
	flushInterval        time.Duration // 定期刷新时间间隔
	maxBatchSize         int           // 最大批量大小
	retryDelay           time.Duration // 重试延迟时间
	maxRetries           int           // 最大重试次数
}

// NewComponentInfoManager 初始化组件信息管理器
func NewComponentInfoManager(bufferSize int, url string, flushInterval time.Duration, maxBatchSize int, retryDelay time.Duration, maxRetries int) *ComponentInfoManager {
	cim := &ComponentInfoManager{
		componentInfoChannel: make(chan *ComponentInfo, bufferSize),
		url:                  fmt.Sprintf("%s/v1/log/add_component", url),
		componentInfos:       make([]*ComponentInfo, 0),
		done:                 make(chan struct{}),
		client: &http.Client{
			Timeout: 10 * time.Second, // 设置 HTTP 客户端超时时间
		},
		flushInterval: flushInterval,
		maxBatchSize:  maxBatchSize,
		retryDelay:    retryDelay,
		maxRetries:    maxRetries,
	}

	cim.wg.Add(1)
	go cim.processComponentInfoChannel()
	return cim
}

// SendComponentInfo 将组件信息发送到组件信息通道
func (cim *ComponentInfoManager) SendComponentInfo(componentInfo *ComponentInfo) {
	cim.componentInfoChannel <- componentInfo
}

// processComponentInfoChannel 处理组件信息通道中的组件信息并批量发送到API
func (cim *ComponentInfoManager) processComponentInfoChannel() {
	defer cim.wg.Done()

	ticker := time.NewTicker(cim.flushInterval)
	defer ticker.Stop()

	for {
		select {
		case componentInfo, ok := <-cim.componentInfoChannel:
			if !ok {
				cim.flushComponentInfos() // 通道关闭时，刷新剩余信息
				return
			}
			cim.handleComponentInfo(componentInfo)
		case <-ticker.C: // 定期刷新
			cim.flushComponentInfos()
		case <-cim.done:
			cim.flushComponentInfos() // 关闭信号时，刷新剩余信息
			return
		}
	}
}

// handleComponentInfo 处理单个组件信息，加锁保证并发安全
func (cim *ComponentInfoManager) handleComponentInfo(componentInfo *ComponentInfo) {
	cim.mu.Lock()
	defer cim.mu.Unlock()
	cim.componentInfos = append(cim.componentInfos, componentInfo)
	// 如果达到最大批量大小，则立即刷新
	if len(cim.componentInfos) >= cim.maxBatchSize {
		cim.flushComponentInfos()
	}
}

// flushComponentInfos 批量发送组件信息到API
func (cim *ComponentInfoManager) flushComponentInfos() {
	cim.mu.Lock()
	// 使用局部变量避免锁竞争
	componentInfos := cim.componentInfos
	// 清空原切片
	cim.componentInfos = make([]*ComponentInfo, 0)
	cim.mu.Unlock()

	if len(componentInfos) == 0 {
		return
	}

	// 异步发送
	go cim.sendComponentInfosToAPI(componentInfos)
}

// sendComponentInfosToAPI 批量发送组件信息到指定的API端点, 异步重试
func (cim *ComponentInfoManager) sendComponentInfosToAPI(componentInfos []*ComponentInfo) {
	payload := struct {
		ComponentInfos []*ComponentInfo `json:"componentInfos"`
	}{
		ComponentInfos: componentInfos,
	}

	data, err := json.Marshal(payload)
	if err != nil {
		logx.Errorf("Failed to marshal component infos: %v", err)
		return
	}

	// 使用 context 控制超时
	ctx, cancel := context.WithTimeout(context.Background(), cim.client.Timeout)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, cim.url, bytes.NewBuffer(data))
	if err != nil {
		logx.Errorf("Failed to create new request: %v", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")

	for i := 0; i < cim.maxRetries; i++ {
		resp, err := cim.client.Do(req)
		if err != nil {
			logx.Errorf("Failed to send component infos to API (attempt %d/%d): %v", i+1, cim.maxRetries, err)
			time.Sleep(cim.retryDelay)
			continue
		}

		// 使用 defer 关闭 response body，并检查 resp 是否为 nil
		if resp != nil {
			defer resp.Body.Close()
		}

		if resp.StatusCode == http.StatusOK {
			return
		}

		logx.Errorf("Failed to send component infos, response code: %d (attempt %d/%d)", resp.StatusCode, i+1, cim.maxRetries)
		time.Sleep(cim.retryDelay)

	}
}

// Close 关闭组件信息通道并等待所有组件信息处理完成
func (cim *ComponentInfoManager) Close() {
	close(cim.componentInfoChannel)
	cim.done <- struct{}{}
	cim.wg.Wait()
}
